package main

import (
    "encoding/json"
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "github.com/micro/go-micro"
    "github.com/micro/go-micro/broker"
    "golang.org/x/crypto/bcrypt"
    "golang.org/x/net/context"
    proto "hello/proto/user"
)

const topic = "user.registered"

// 服务端提供的服务端口
type service struct {
    repo    Repository
    PubSub  broker.Broker
}

func (srv *service) Get(ctx context.Context, req *proto.User, res *proto.Response) error {
    user, err := srv.repo.Get(req.Id)
    if err != nil {
        return err
    }
    res.User = user
    return nil
}

func (srv *service) Create(ctx context.Context, req *proto.User, res *proto.Response) error {
    // Generates a hashed version of our password
    hashedPass, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
    if err != nil {
        return err
    }
    req.Password = string(hashedPass)
    if err := srv.repo.Create(req); err != nil {
        return err
    }

    res.User = req
    body, err := json.Marshal(res.User)
    if err != nil {
        return err
    }

    // Create a broker message
    msg := &broker.Message{
        Header: map[string]string{
            "id": res.User.Id,
        },
        Body: body,
    }
    if err := srv.PubSub.Publish(topic, msg); err != nil {
        fmt.Errorf("publish message failed: %v\n", err)
        return err
    }

    return nil
}

// 建立数据库连接
func CreateConnection() (*gorm.DB, error) {
    // Get database details from environment variables
    host := "localhost"
    port := "3306"
    user := "root"
    password := "root"
    DbName := "greeter"

    return gorm.Open(
        "mysql",
        fmt.Sprintf(
            "%s:%s@(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
            user, password, host, port, DbName,
        ),
    )
}

// 通过 Repository 与数据库进行交互
type Repository interface {
    Get(id string) (*proto.User, error)
    Create(user *proto.User) error
    GetByEmail(email string) (*proto.User, error)
}

type UserRepository struct {
    db *gorm.DB
}

func (repo *UserRepository) Get(id string) (*proto.User, error) {
    var user proto.User
    user.Id = id
    if err := repo.db.First(&user).Error; err != nil {
        return nil, err
    }
    return &user, nil
}

func (repo *UserRepository) Create(user *proto.User) error {
    if err := repo.db.Create(user).Error; err != nil {
        return err
    }
    return nil
}

func (repo *UserRepository) GetByEmail(email string) (*proto.User, error) {
    user := &proto.User{}
    if err := repo.db.Where("email = ?", email).First(&user).Error; err != nil {
        return nil, err
    }
    return user, nil
}

//  UserService 服务端入口函数
func main() {
    // Creates a database connection and handles
    // closing it again before exit.
    db, err := CreateConnection()
    defer db.Close()

    if err != nil {
        fmt.Errorf("Could not connect to DB: %v\n", err)
    }

    // Automatically migrates the user struct
    // into database columns/types etc. This will
    // check for changes and migrate them each time
    // this service is restarted.
    db.AutoMigrate(&proto.User{})

    repo := &UserRepository{db}

    // Create a new service. Optionally include some options here.
    srv := micro.NewService(
        // This name must match the package name given in your protobuf definition
        micro.Name("go.micro.srv.user"),
        micro.Version("latest"),
    )

    // Init will parse the command line flags.
    srv.Init()

    pubsub := srv.Server().Options().Broker

    // Register handler
    proto.RegisterUserServiceHandler(srv.Server(), &service{repo, pubsub})

    // Run the server
    if err := srv.Run(); err != nil {
        fmt.Println(err)
    }
}